Skip to main content

Class 6 - Score, Currency & Upgrades


本节课承接上一节课(Class 5)的 UI 与生命值系统,继续完善 SpaceShooter 的“游玩逻辑”:得分、货币与五项升级功能,并完成按钮事件绑定与最终测试。

Class 6 Overview

  1. 得分与货币系统
  2. 五项升级功能(Repair、Hull Strength、Fire Speed、Missile Speed、Multiplier)
  3. 按钮事件绑定
  4. 最终测试清单

1. 得分与货币系统(GameController)

GameController.cs
using TMPro;

public class GameController : MonoBehaviour
{
public static GameController instance;

// 记得要在Inspector中拖拽赋值哦
public TMP_Text textScore;
public TMP_Text textMoney;

public int score = 0;
public int money = 0;

void Update() {
UpdateDisplay();
}

void UpdateDisplay() {
textScore.text = score.ToString();
textMoney.text = money.ToString();
}

public void EarnPoints(int amount) {
score += amount;
money += amount;
}
}

接下来转到Projectile.cs,在击毁陨石时,也就是在OnCollisionEnter2D函数中加上:调用刚写的EarnPoints()函数

Projectile.cs
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.GetComponent<Asteroid>() != null)
{
Destroy(other.gameObject);
Destroy(gameObject);

GameObject explosion = Instantiate(
GameController.instance.explosionPrefab,
transform.position,
Quaternion.identity
);
Destroy(explosion, 0.25f);

// 加这里!!得分 & 加钱逻辑
GameController.instance.EarnPoints(10);
}
}

2. 五项升级功能

2.1 Repair(Ship)

Ship.cs
public void RepairHull()
{
int cost = 100;
if (GameController.instance.money >= cost && health < maxHealth && health > 0)
{
GameController.instance.money -= cost;

health = maxHealth;
imageHealthBar.fillAmount = health / maxHealth;
}
}

然后我们需要去 Button 的 Inspector 里面,找到 OnClick 事件。把 Ship 对象拖过来绑定,就可以获取到它身上携带的脚本,从而可以选择RepairHull()
这样做的意思就是:当按钮被点击时,调用Ship身上的RepairHull()函数。 Button_OnClick

2.2 Hull Strength(Ship)

Ship.cs
using TMPro;

namespace SpaceShooter
{
public class Ship : MonoBehaviour
{
// 记得拖拽赋值
public TMP_Text hullUpgradeText;

public void UpgradeHull() {
// 升级费用等于当前血量上限
int cost = Mathf.RoundToInt(healthMax);

if(GameController.instance.money >= cost) {
GameController.instance.money -= cost;

health += 50;
maxHealth += 50;
imageHealthBar.fillAmount = health / maxHealth;

hullUpgradeText.text = "Hull Strength $" + Mathf.RoundToInt(maxHealth);
}
}
}
}

同上,OnClick 绑定 UpgradeHull()

String + int?
hullUpgradeText.text = "Hull Strength $" + Mathf.RoundToInt(maxHealth);

我们在这行代码可以看到,text作为一个字符串类型的对象,却可以通过 string + int 的形式实现拼接:
这是因为C#中,字符串在拼接的时候,会自动将不是字符串的类型加一个ToString()转换为字符串类型,这被称之为隐式类型转换

2.3 Fire Speed(Ship)

Ship.cs
namespace SpaceShooter
{
public class Ship : MonoBehaviour
{
public TMP_Text fireSpeedUpgradeText;

public void UpgradeFireSpeed() {
// 由于开火间隔越小,也就是射速越快,费用应该越高。所以我们需要在firingDelay前面加一个负号
int cost = 100 + Mathf.RoundToInt((1f - firingDelay) * 100f);

if(GameController.instance.money >= cost) {
GameController.instance.money -= cost;

// 这样降低间隔会没有下限,会出现bug
// firingDelay -=0.05f;

// 所以我们需要用Mathf.Clamp来限制下限
firingDelay = Mathf.Clamp(firingDelay - 0.05f, 0.1f, float.MaxValue);

int newCost = 100 + Mathf.RoundToInt((1f - firingDelay) * 100f);
fireSpeedUpgradeText.text = "Fire Speed $" + newCost;
}
}
}
}

2.4 Missile Speed(GameController + Projectile)

GameController.cs
namespace SpaceShooter
{
public class GameController : MonoBehaviour
{
public TMP_Text missileSpeedUpgradeText;
public float missileSpeed = 2f;
}
}

既然这边声明好了一个新的子弹速度的变量,那我们就可以回去Projectile.cs里面,把之前直接写死的子弹速度和加速度,改成与GameController中刚声明的missileSpeed相关的变量:

Projectile.cs
void Update() {
float acceleration = GameController.instance.missileSpeed * 0.5f;
float maxSpeed = GameController.instance.missileSpeed;
}

回到GameController.cs中,实现UpgradeMissileSpeed()函数:

GameController.cs
public void UpgradeMissileSpeed() {
int cost = Mathf.RoundToInt(missileSpeed * 25f);

if(money >= cost) {
money -= cost;

missileSpeed += 1f;

missileSpeedUpgradeText.text = "Missile Speed $" + Mathf.RoundToInt(missileSpeed * 25f);
}
}

2.5 Multiplier(GameController)

GameController.cs
namespace SpaceShooter
{
public class GameController : MonoBehaviour
{
public TMP_Text bonusUpgradeText;
public int bonusMultiplier = 1;

public void EarnPoints(int amount) {
score += amount * bonusMultiplier;
money += amount * bonusMultiplier;
}

public void UpgradeBonus() {
int cost = Mathf.RoundToInt(bonusMultiplier * 100);

if(money >= cost) {
money -= cost;

bonusMultiplier++;

bonusUpgradeText.text = "Bonus Multiplier $" + Mathf.RoundToInt(bonusMultiplier * 100);
}
}
}
}

总结

那么到此,我们的第一个练手小游戏 SpaceShooter 就完成了。 在这一系列课程中,我们从零基础开始,一步步完成了一个小型2D射击游戏的开发过程。让我们回顾一下主要的学习路径:

  1. 起步阶段
  • 安装开发环境
  • 导入游戏所需的素材资源
  1. 核心控制
  • 实现基础的 WASD 飞船移动控制
  • 引入物理系统,学习了 RigidbodyCollider 两个常见组件
  • 基于物理组件,构建了几种不同的飞船控制逻辑(静态移动、基于物理的旋转与推进)
  1. 游戏表现
  • 添加动画系统,使角色和物体更加生动
  • 学习了 协程(Coroutine) 的概念,用于实现复杂的时间控制逻辑
  • 制作了敌人生成与子弹系统,实现了最初的 AI 行为逻辑(如自动锁定目标)
  1. 完整玩法
  • 补齐了 UI 界面,包括血量系统、得分与货币显示
  • 实现了升级功能,让游戏形成完整的游玩循环